/*
 * Copyright (c) 2023 IPADS, Shanghai Jiao Tong University.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <bitset>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

#include "DDSClient.h"
#include "DDSServer.h"
#include "Softbus.h"

#include <PLEnclaveEngine.h>
#include <cstdlib>
#include <face_recognition_shared_param.h>
#include <pthread.h>

using namespace eprosima::fastrtps;
using namespace eprosima::fastrtps::rtps;
using namespace enclaveelf;
using namespace demoparam;
using namespace penglaienclave;
using std::cout;
using std::endl;

enum class E_SIDE {
    CLIENT,
    SERVER
};

struct args {
    void *in;
    int i;
};

static void run_enclave_with_args(struct Enclave *enclave)
{
    uint8_t *untrusted_mem_extent = nullptr;
    face_recognition_shared_param_t face_recognition_shared_param;

    char face_information_list[INFORMATION_SIZE] = {0};
    for (unsigned long i = 0; i < faceInformation.size(); i++) {
        face_information_list[i] = faceInformation[i];
    }
    memset_s(face_recognition_shared_param.faceInformation,
             sizeof(face_recognition_shared_param.faceInformation), 0,
             sizeof(face_recognition_shared_param.faceInformation));
    memcpy_s(&face_recognition_shared_param.faceInformation,
             sizeof(face_information_list), face_information_list,
             sizeof(face_information_list));
    face_recognition_shared_param.face_information_size =
        faceInformation.size();

    untrusted_mem_extent = (uint8_t *)malloc(DEFAULT_UNTRUSTED_SIZE);
    memcpy_s(untrusted_mem_extent, sizeof(face_recognition_shared_param),
             &face_recognition_shared_param,
             sizeof(face_recognition_shared_param));

    // trans args by untrusted mem
    enclave->user_param.untrusted_mem_ptr = (unsigned long)untrusted_mem_extent;
    enclave->user_param.untrusted_mem_size = DEFAULT_UNTRUSTED_SIZE;

    PLenclave_run(enclave);

    // handle outputs
    face_recognition_shared_param_t *face_recognition_shared_param_after_run =
        (face_recognition_shared_param_t *)untrusted_mem_extent;
    char face_recognition_result_list[INFORMATION_SIZE];
    memset_s(face_recognition_result_list, sizeof(face_recognition_result_list),
             0, sizeof(face_recognition_result_list));
    memcpy_s(
        &face_recognition_result_list,
        sizeof(face_recognition_shared_param_after_run->faceRecognitionResult),
        face_recognition_shared_param_after_run->faceRecognitionResult,
        sizeof(face_recognition_shared_param_after_run->faceRecognitionResult));
    std::string face_recognition_result_str;
    for (int i = 0;
         i <
         face_recognition_shared_param_after_run->face_recognition_result_size;
         i++) {
        face_recognition_result_str.push_back(face_recognition_result_list[i]);
    }
    std::cout << "face_recognition_result_str = " << face_recognition_result_str
              << std::endl;
    faceRecognitionResult = face_recognition_result_str;
}

static void server_create_enclave(struct args *args0)
{
    struct args *args = args0;
    void *in = args->in;
    int i = args->i;

    penglaienclave::Enclave *enclave = static_cast<penglaienclave::Enclave *>(
        malloc(sizeof(struct penglaienclave::Enclave)));
    if (enclave == nullptr) {
        return;
    }
    struct enclave_args *params =
        static_cast<enclave_args *>(malloc(sizeof(struct enclave_args)));
    if (params == nullptr) {
        return;
    }
    PLenclave_init(enclave);
    enclave_param_init(params);

    struct enclaveelf::elf_args *enclaveFile =
        static_cast<struct enclaveelf::elf_args *>(in);
    params->untrusted_mem_size = DEFAULT_UNTRUSTED_SIZE;
    params->untrusted_mem_ptr = 0;
    if (PLenclave_create(enclave, enclaveFile, params) < 0) {
        printf("host:%d: failed to create enclave\n", i);
    } else {
        PLenclave_attest(enclave, NONCE);

        run_enclave_with_args(enclave);
    }
    PLenclave_finalize(enclave);
    enclave_param_destroy(params);
    free(enclave);
    free(params);
}

static int create_penglai_enclave(std::string enclave_name)
{
    struct args *args = static_cast<struct args *>(malloc(sizeof(struct args)));
    if (args == nullptr) {
        return -1;
    }
    struct enclaveelf::elf_args *enclaveFile =
        static_cast<enclaveelf::elf_args *>(
            malloc(sizeof(struct enclaveelf::elf_args)));
    if (enclaveFile == nullptr) {
        return -1;
    }
    enclaveelf::elf_args_init(enclaveFile,
                              const_cast<char *>(enclave_name.c_str()));

    if (!enclaveelf::elf_valid(enclaveFile)) {
        printf("error when initializing enclaveFile\n");
    }

    args->in = enclaveFile;
    args->i = 1;
    server_create_enclave(args);

    enclaveelf::elf_args_destroy(enclaveFile);
    free(enclaveFile);
    free(args);

    return 0;
}

static int PLenclave_remote(std::string enclave_name)
{
    create_penglai_enclave(enclave_name);
    return 0;
}

static int PLenclave_operation_remote(std::string enclave_name)
{
    PLenclave_remote(enclave_name);
    return 0;
}

static int check_args(E_SIDE *side, int argc, char **argv, int samples)
{
    if (argc > ARG_ONE) {
        if (strcmp(argv[ARG_ONE], "client") == 0) {
            *side = E_SIDE::CLIENT;
        } else if (strcmp(argv[ARG_ONE], "server") == 0) {
            *side = E_SIDE::SERVER;
        } else {
            cout << "Argument 1 needs to be client OR server" << endl;
            return 0;
        }
    } else {
        cout << "Client Server Test needs 1 arguments: (client/server)" << endl;
        return 0;
    }
}

int main(int argc, char **argv)
{
    cout << "Starting " << endl;
    E_SIDE side;
    int samples;

    if (!check_args(&side, argc, argv, samples)) {
        return 0;
    };

    if (side == E_SIDE::SERVER) {
        SoftbusServer server;
        server.publish_service("PLenclave_operation_remote",
                               PLenclave_operation_remote);
        server.run();
    }
    if (side == E_SIDE::CLIENT) {
        char *eappfile = argv[ARG_TWO];
        std::string enclave_name = eappfile;
        SoftbusClient<> client;

        int result = client.call_service<int>("PLenclave_operation_remote",
                                              ENCLAVE_UNRELATED, enclave_name);
    }

    cout << "EVERYTHING STOPPED FINE" << endl;
}
